//
//  ZYDiskCache.m
//  ZYWebImage
//
//  Created by wangzhipan on 2025/3/14.
//

#import "ZYDiskCache.h"
#import "ZYDiskConfig.h"
#import "ZYWebImageConst.h"

@interface ZYDiskCache()
@property (nonatomic, copy, nullable) NSString *cachePath;
@property (nonatomic, strong, nullable) ZYDiskConfig *config;
@property (nonatomic, strong, nullable) NSFileManager *fileMgr;
@end

@implementation ZYDiskCache
- (instancetype)initWithCachePath:(NSString *)cachePath 
                           config:(ZYDiskConfig *)config
{
    if (self = [super init]) {
        self.cachePath = cachePath;
        self.config = config;
        self.fileMgr = [NSFileManager new];
        [self p_createDirectory];
    }
    return self;
}

- (BOOL)containDataForKey:(NSString *)key
{
    NSString *filePath = [self cachePathForKey:key];
    return [self.fileMgr fileExistsAtPath:filePath];
}

- (nullable NSData *)dataForKey:(NSString *)key
{
    NSString *filePath = [self cachePathForKey:key];
    if (filePath == nil || [filePath isEqualToString:@"(null)"]) {
        return nil;
    }
    
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    if (data) {
        //设置最近一次访问时间
        [[NSURL URLWithString:filePath] setResourceValue:[NSDate date] forKey:NSURLContentAccessDateKey error:nil];
    }
    return data;
}

- (nullable NSData *)extraDataForKey:(NSString *)key
{
    NSString *filePath = [self p_extraPathForKey:key];
    if (filePath == nil || [filePath isEqualToString:@"(null)"]) {
        return nil;
    }
    return [NSData dataWithContentsOfFile:filePath];
}

- (void)saveData:(nullable NSData *)data forKey:(nonnull NSString *)key
{
    if (!data || !key) {
        return;
    }
    NSString *filePath = [self cachePathForKey:key];
    NSURL *fileUrl = [NSURL fileURLWithPath:filePath isDirectory:NO];
    [data writeToURL:fileUrl options:NSDataWritingAtomic error:nil];
}

- (void)saveExtraData:(NSData *)data forKey:(NSString *)key
{
    if (!data || !key) {
        return;
    }
    NSString *filePath = [self p_extraPathForKey:key];
    NSURL *fileUrl = [NSURL fileURLWithPath:filePath isDirectory:NO];
    [data writeToURL:fileUrl options:NSDataWritingAtomic error:nil];
}

- (void)removeDataForKey:(nonnull NSString *)key
{
    NSString *filePath = [self cachePathForKey:key];
    [self.fileMgr removeItemAtPath:filePath error:nil];
}

- (void)removeAllData
{
    [self.fileMgr removeItemAtPath:self.cachePath
                             error:nil];
    [self p_createDirectory];
}

- (void)removeExpiredData
{
    NSURL *diskDirectoryUrl = [NSURL fileURLWithPath:self.cachePath
                                         isDirectory:YES];
    NSURLResourceKey accessDateKey = NSURLContentAccessDateKey;
    
    NSArray<NSString *> *resourceKeys = @[NSURLIsDirectoryKey, accessDateKey, NSURLTotalFileAllocatedSizeKey];
    
    NSDirectoryEnumerator<NSURL *> *enumerator = [self.fileMgr
                                                  enumeratorAtURL:diskDirectoryUrl
                                                  includingPropertiesForKeys:resourceKeys
                                                  options:NSDirectoryEnumerationSkipsHiddenFiles
                                                  errorHandler:nil];
    
    /*
     1、移除超过最大保存日期的文件
     2、检测一下是否超过最大磁盘限制，超过的话，按访问时间从小到大排序，删除文件，直到size只剩下 maxSize / 2
     */
    NSDate *expiredDate = (self.config.maxAge < 0) ? nil : [NSDate dateWithTimeIntervalSinceNow:-self.config.maxAge];
    NSUInteger totalSize = 0;
    
    //过期文件,需要删除
    NSMutableArray <NSURL *> *toDelUrls = [NSMutableArray array];
    NSMutableDictionary *cacheFiles = [NSMutableDictionary dictionary];
    for (NSURL *fileUrl in enumerator) {
        @autoreleasepool {
            NSError *error;
            NSDictionary<NSString *, id> *resourceValues = [fileUrl
                                                            resourceValuesForKeys:resourceKeys
                                                            error:&error];
            //有错误、是文件，则取下一个
            if (error || !resourceValues || [resourceValues[NSURLIsDirectoryKey] boolValue]) {
                continue;;
            }
            
            //取出文件修改时间
            NSDate *modifiedDate = resourceValues[accessDateKey];
            if (expiredDate && [[expiredDate laterDate:modifiedDate] isEqualToDate:expiredDate]) {
                [toDelUrls addObject:fileUrl];
                continue;;
            }
            
            NSUInteger fileSize = [resourceValues[NSURLTotalFileAllocatedSizeKey] unsignedIntValue];
            totalSize += fileSize;
            cacheFiles[fileUrl] = resourceValues;
        }
    }
    
    //先删除过期文件
    for (NSURL *fileUrl in toDelUrls) {
        [self.fileMgr removeItemAtURL:fileUrl error:nil];
    }
    
    //再计算，超过磁盘大小的话，只保留一半磁盘限制大小
    NSUInteger maxDiskSize = self.config.maxSize;
    if (maxDiskSize > 0 && totalSize >= maxDiskSize) {
        const NSUInteger enabelSize = maxDiskSize / 2;
        NSUInteger currentSize = totalSize;
        NSArray <NSURL *> *urls = [cacheFiles keysSortedByValueUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
            return [obj1[accessDateKey] compare:obj2[accessDateKey]];
        }];
        
        for (NSURL *fileUrl in urls) {
            if ([self.fileMgr removeItemAtURL:fileUrl error:nil]) {
                NSDictionary<NSString *, id> *resourceValues = cacheFiles[fileUrl];
                NSUInteger fileSize = resourceValues[NSURLTotalFileAllocatedSizeKey];
                currentSize -= fileSize;
                if (currentSize <= enabelSize) {
                    break;
                }
            }
        }
    }
}

- (nullable NSString *)cachePathForKey:(nonnull NSString *)key
{
    NSString *fileName = ZYDiskCacheFileNameForKey(key);
    return [self.cachePath stringByAppendingPathComponent:fileName];
}
- (NSUInteger)totalCount
{
    NSUInteger count = 0;
    @autoreleasepool {
        NSURL *diskCacheURL = [NSURL fileURLWithPath:self.cachePath isDirectory:YES];
        NSDirectoryEnumerator<NSURL *> *fileEnumerator = [self.fileMgr enumeratorAtURL:diskCacheURL includingPropertiesForKeys:@[] options:0 errorHandler:nil];
        count = fileEnumerator.allObjects.count;
    }
    return count;
}
//return byte
- (NSUInteger)totalSize
{
    NSUInteger size = 0;

    // Use URL-based enumerator instead of Path(NSString *)-based enumerator to reduce
    // those objects(ex. NSPathStore2/_NSCFString/NSConcreteData) created during traversal.
    // Even worse, those objects are added into AutoreleasePool, in background threads,
    // the time to release those objects is undifined(according to the usage of CPU)
    // It will truely consumes a lot of VM, up to cause OOMs.
    @autoreleasepool {
        NSURL *pathURL = [NSURL fileURLWithPath:self.cachePath isDirectory:YES];
        NSDirectoryEnumerator<NSURL *> *fileEnumerator = [self.fileMgr enumeratorAtURL:pathURL
                                                  includingPropertiesForKeys:@[NSURLFileSizeKey]
                                                                     options:(NSDirectoryEnumerationOptions)0
                                                                errorHandler:NULL];
        
        for (NSURL *fileURL in fileEnumerator) {
            @autoreleasepool {
                NSNumber *fileSize;
                [fileURL getResourceValue:&fileSize forKey:NSURLFileSizeKey error:NULL];
                size += fileSize.unsignedIntegerValue;
            }
        }
    }
    return size;
}


- (void)p_createDirectory
{
    [self.fileMgr createDirectoryAtPath:self.cachePath
            withIntermediateDirectories:YES
                             attributes:nil
                                  error:nil];
}

- (NSString *)p_extraPathForKey:(NSString *)key
{
    if (!key) {
        return nil;
    }
    key = [key stringByAppendingString:@"extarData"];
    return [self cachePathForKey:key];
}
@end
